import { Component, cls, type StyledProps } from '@knno/jsx';
// import SVGApp from '@mdi/svg/svg/apps.svg';
import SVGchevronUp from '../../svg/chevron-up.svg';
import { autoHideScrollBar } from '../../theme/scrollbar.css';
import { defineElement } from '../defineElement';
import { Foldable } from '../foldable/foldable';
import { Icon } from '../icon/icon';
import { DATA } from '../symbol';
import { TableList } from '../tableList/tableList';
import { foldableMenuStyle } from './foldableMenu.css';

type FoldableMenuItem = {
	key?: string;
	label: string;
	icon?: string;
	expand?: boolean;
	children?: FoldableMenuItem[];
	/**
	 * Do not use this field to store any value,
	 * this field will be reassigned when set menu data to component.
	 */
	_path?: string;
};

const MenuElement = defineElement('knno-foldable-menu', HTMLDivElement, 'div');

function equal(item1: FoldableMenuItem, item2: FoldableMenuItem): boolean {
	return item1 === item2;
}

function isMatchedItem(menuItem: FoldableMenuItem, activeItem: FoldableMenuItem | null): boolean {
	if (activeItem === null) return false;
	if (equal(menuItem, activeItem)) return true;
	if (menuItem.children) {
		for (const child of menuItem.children) {
			if (isMatchedItem(child, activeItem)) {
				return true;
			}
		}
	}
	return false;
}

export interface FoldableMenuProps extends StyledProps<FoldableMenu> {
	items: FoldableMenuItem[];
	/**
	 * Active menu item by data item(highest priority)
	 */
	selected?: FoldableMenuItem;
	/**
	 * Active menu item by item path(secondary priority)
	 */
	selectedPath?: number[];
	/**
	 * Active menu item by item key(lowest priority)
	 */
	selectedKey?: string;
	/**
	 * Only one group can be expanded at the same time.
	 */
	singleGroupMode?: boolean;
	aniDuration?: number;
	onConnected?: (this: FoldableMenu) => void;
	onDisconnected?: (this: FoldableMenu) => void;
	onMenuItemClick?: (this: FoldableMenu, item: FoldableMenuItem, path: string) => void;
}

type Group = {
	caption: HTMLDivElement;
	foldable?: Foldable;
	list?: TableList;
};

export class FoldableMenu extends Component<FoldableMenuProps> {
	protected render(): Node {
		return (
			<MenuElement
				class={cls(this.props.class, foldableMenuStyle, autoHideScrollBar)}
				style={this.props.style}
				onConnected={() => this.props.onConnected?.call(this)}
				onDisconnected={() => this.props.onDisconnected?.call(this)}
			/>
		);
	}
	[DATA]: Group[] = [];
	constructor(props: FoldableMenuProps) {
		super(props);
		this.setItems(this.items);
		if (props.selected !== undefined) {
			this.setSelected(props.selected ?? null, true);
		} else if (props.selectedPath) {
			this.setSelectedPath(props.selectedPath, true);
		} else if (props.selectedKey) {
			this.setSelectedKey(props.selectedKey, true);
		}
	}

	private renderContent() {
		this[DATA].length = 0;
		this.el.replaceChildren(
			...this.props.items.map((item, idx) => {
				const selectItem = () => {
					if (item.children && item.children.length > 0) {
						const expanded = group.foldable?.expanded ?? false;
						if (expanded) this.collapseGroup(idx);
						else this.expandGroup(idx);
						if (this.props.singleGroupMode) {
							this[DATA].forEach((g, i) => {
								if (i !== idx) {
									this.collapseGroup(i);
								}
							});
						}
					} else {
						this.setSelected(item);
						this.props.onMenuItemClick?.call(this, item, this.getItemKey(item) ?? '');
					}
				};
				const caption = (
					<div
						class={['caption', item.children && item.children.length > 0 ? 'group' : '']}
						tabIndex={0}
						onClick={() => {
							selectItem();
						}}
						onKeyPress={(evt) => {
							if (evt.key === 'Enter') {
								selectItem();
							}
						}}
						onKeyDown={(evt) => {
							if (evt.key === 'ArrowDown' && item.children && item.children.length > 0) {
								group.list?.setActive(0);
								group.list?.el.focus();
							}
						}}
					>
						<div class="icon">{item.icon ? <Icon svg={item.icon} /> : ''}</div>
						<div class="label">{item.label}</div>
						<div class="arrow">
							<Icon svg={SVGchevronUp} />
						</div>
					</div>
				) as HTMLDivElement;
				const group: Group = {
					caption,
				};
				if (item.children && item.children.length > 0) {
					group.list = new TableList({
						column: {
							value: '_path',
							text: 'label',
							children: 'children',
							expand: 'expand',
						},
						activable: true,
						rows: item.children ?? [],
						onRowClick: (detail) => {
							// eslint-disable-next-line @typescript-eslint/no-explicit-any
							const row = detail.row as any;
							this.setSelected(row.row);
							this.props.onMenuItemClick?.call(this, row.row, this.getItemKey(row.row) ?? '');
						},
						onRowPress: (detail) => {
							// eslint-disable-next-line @typescript-eslint/no-explicit-any
							const row = detail.row as any;
							this.setSelected(row.row);
							this.props.onMenuItemClick?.call(this, row.row, this.getItemKey(row.row) ?? '');
						},
					});
					group.foldable = new Foldable({
						children: group.list.el,
						aniDuration: this.props.aniDuration,
					});
				}
				this[DATA].push(group);
				return (
					<>
						{group.caption}
						{group.foldable?.el}
					</>
				);
			}),
		);
	}

	getItemPath(item: FoldableMenuItem): number[] {
		const getPath = (items: FoldableMenuItem[], item: FoldableMenuItem): number[] => {
			for (let i = 0; i < items.length; i++) {
				if (equal(item, items[i])) {
					return [i];
				} else {
					const children = items[i].children ?? [];
					const subPath = getPath(children, item);
					if (subPath.length > 0) {
						return [i, ...subPath];
					}
				}
			}
			return [];
		};
		return getPath(this.items, item);
	}

	getItemKey(item: FoldableMenuItem): string | null {
		const getPath = (items: FoldableMenuItem[], item: FoldableMenuItem): string | null => {
			for (let i = 0; i < items.length; i++) {
				if (equal(item, items[i])) {
					return items[i].key ?? `#${i}`;
				} else {
					const children = items[i].children ?? [];
					const subPath = getPath(children, item);
					if (subPath !== null) {
						return `${items[i].key ?? `#${i}`}/${subPath}`;
					}
				}
			}
			return null;
		};
		return getPath(this.items, item);
	}

	getItemByPath(path: number[]): FoldableMenuItem | null {
		let items = this.items;
		let finded: FoldableMenuItem | null = null;
		for (let i = 0; i < path.length; i++) {
			const idx = path[i];
			finded = items[idx];
			if (finded) {
				items = finded.children ?? [];
			} else {
				return null;
			}
		}
		return finded;
	}

	getItemByKey(key: string): FoldableMenuItem | null {
		const keyArray = key.split('/');
		let items = this.items;
		let finded: FoldableMenuItem | undefined;
		for (let i = 0; i < keyArray.length; i++) {
			const key = keyArray[i];
			finded = items.find((item, idx) =>
				item.key !== undefined ? item.key === key : `#${idx}` === key,
			);
			if (finded) {
				items = finded.children ?? [];
			} else {
				return null;
			}
		}
		return finded ?? null;
	}

	get items(): FoldableMenuItem[] {
		if (this.props.items === undefined) {
			this.props.items = [];
		}
		return this.props.items;
	}
	setItems(items: FoldableMenuItem[]): this {
		this.props.items = items;
		function setItemsPath(items: FoldableMenuItem[], parentPath: string) {
			for (let i = 0; i < items.length; i++) {
				const path = parentPath ? `${parentPath}/${i}` : `${i}`;
				items[i]._path = path;
				const children = items[i].children;
				if (children !== undefined) {
					setItemsPath(children, path);
				}
			}
		}
		setItemsPath(this.props.items, '');
		this.renderContent();
		return this;
	}

	get selectedPath(): number[] {
		return this.props.selected ? this.getItemPath(this.props.selected) : [];
	}
	setSelectedPath(path: number[], noAni = false): this {
		const item = this.getItemByPath(path);
		return this.setSelected(item, noAni);
	}

	get selectedKey(): string | null {
		return this.props.selected ? this.getItemKey(this.props.selected) : null;
	}

	setSelectedKey(key: string | null, noAni = false): this {
		if (key !== null) {
			if (key.startsWith('#/')) {
				key = key.substring(2);
			}
			const item = this.getItemByKey(key);
			return this.setSelected(item, noAni);
		} else {
			return this.setSelected(null, noAni);
		}
	}

	get selected(): FoldableMenuItem | null {
		return this.props.selected ?? null;
	}
	setSelected(item: FoldableMenuItem | null, noAni = false): this {
		this.props.selected = item ?? undefined;
		for (let i = 0; i < this[DATA].length; i++) {
			const group = this[DATA][i];
			if (item && isMatchedItem(this.props.items[i], item)) {
				this.expandGroup(i, noAni);
				group.list?.setActiveValue(this.getItemPath(item).join('/'));
			} else {
				if (this.props.singleGroupMode || !group.list) {
					this.collapseGroup(i, noAni);
				}
				group.list?.setActive(null);
			}
		}
		return this;
	}

	expandGroup(idx: number, noAni = false): this {
		if (idx >= 0 && idx < this.props.items.length) {
			const group = this[DATA][idx];
			group.caption.classList.add('selected');
			group.foldable?.setExpanded(true, noAni);
		}
		return this;
	}

	collapseGroup(idx: number, noAni = false): this {
		if (idx >= 0 && idx < this.props.items.length) {
			const group = this[DATA][idx];
			group.caption.classList.remove('selected');
			group.foldable?.setExpanded(false, noAni);
		}
		return this;
	}
}
